### Вложенные классы

это когда один класс объявлен внутри другого. 

в `Python` так тоже можно делать.

И здесь сразу возникает два главных вопроса: как это работает и зачем это надо? Я

На мой взгляд, хороший пример использования вложенных классов – это описание моделей во фреймворке `Django`. 

Если вы не знаете, что это такое, то не беспокойтесь, нам нужна будет только суть вложений. 

Итак, там часто можно встретить определения вида:

```python
class Women:
    title = 'объект класса для поля title'
    photo = 'объект класса для поля photo'
 
    class Meta:
        ordering = ['id']
```

Здесь объявляется некий класс `Women`, далее прописаны два атрибута, которые в `Django` являются ссылками на определенные классы, но в нашем примере – это просто строки. 

Причем, атрибуты `title и photo` – это поля в таблице БД, с которой впоследствии идет работа, то есть, полагаем, что нам важно называть эти атрибуты именно такими именами.

Затем, идет объявление вложенного класса `Meta` с одним атрибутом `ordering`. 

В` Django` с помощью такого класса `Meta` определяют некие вспомогательные параметры. 

В частности, параметр `ordering` определяет сортировку по полю `id` записей в таблице БД. 

Главное сейчас, что у нас есть класс `Women`, в нем атрибуты со строго определенными именами и вложенный класс `Meta` с одним атрибутом `ordering`. 

Это все, что нужно сейчас знать.

![](img/image001.png)

Но все же, зачем нужен такой вложенный класс? 

Почему бы этот атрибут не прописать непосредственно в классе Women? 

Давайте предположим, что в нашей таблице есть поле с именем `ordering` и для работы с ним в классе `Women` его следует прописать, сделаем это так:

```python
class Women:
    title = 'объект класса для поля title'
    photo = 'объект класса для поля photo'
    ordering = 'объект класса для поля ordering'
 
    class Meta:
        ordering = ['id']
```

Видите, если бы не было вложенного класса `Meta`, то возникла бы проблема с использованием одного и того же атрибута ordering для разных целей: и для представления данных поля ordering и для управления сортировкой.

Вложенный класс `Meta`, фактически, создает независимое пространство имен для разрешения подобных конфликтов.

![](img/image002.png)

Разумеется, мы можем совершенно спокойно обращаться ко всем атрибутам класса через основной класс Women, например, так:

```python
print(Women.ordering)
print(Women.Meta.ordering)
```

В первом случае берем атрибут ordering непосредственно из класса Women, а во втором – из класса Meta.

То же самое можно делать и через экземпляры класса Women:

```python
w = Women()
print(w.ordering)
print(w.Meta.ordering)
```

Причем внутри самого объекта у нас сейчас не будет никаких локальных свойств:

```python
print(w.__dict__)
```

Давайте для примера добавим инициализатор в класс Women, запишем его следующим образом:

```python

class Women:
    title = 'объект класса для поля title'
    photo = 'объект класса для поля photo'
    ordering = 'объект класса для поля ordering'
 
    def __init__(self, user, psw):
        self._user = user
        self._psw = psw
 
    class Meta:
        ordering = ['id']
```

Теперь, при создании объектов:
```python
w = Women('root', '12345')
print(w.__dict__)
```

в них дополнительно будут создаваться локальные свойства user и psw:

```python
{'_user': 'root', '_psw': '12345'}
```
![](img/image003.png)

Но обратите внимание, объект класса Meta при этом не создается. 

Здесь `Meta` – это, по сути, еще один атрибут (пространство имен) в классе Women и мы к нему обращаемся через `w` как к атрибуту класса, не более того. 

Если нам нужен объект класса Meta, его нужно явно создать в инициализаторе класса Women:

```python
def __init__(self, user, psw):
        self._user = user
        self._psw = psw
        self.meta = self.Meta()
```

Теперь в экземпляре класса Women имеется ссылка `meta` на объект класса `Meta`. Чтобы в этой операции был смысл, давайте пропишем инициализатор во вложенном классе:

```python
class Women:
    title = 'объект класса для поля title'
    photo = 'объект класса для поля photo'
    ordering = 'объект класса для поля ordering'
 
    def __init__(self, user, psw):
        self._user = user
        self._psw = psw
        self.meta = self.Meta(user + '@' + psw)
 
    class Meta:
        ordering = ['id']
 
        def __init__(self, access):
            self._access = access

```

Теперь, при создании объекта класса `Women` будут автоматически создаваться два объекта: класса `Women` и класса `Meta` с соответствующими локальными свойствами:

```python
w = Women('root', '12345')
print(w.__dict__)
print(w.meta.__dict__)
```

И, обратите внимание, мы не можем из класса `Meta` или из его объектов обращаться к атрибутам класса `Women`.

Это совершенно изолированное пространство имен от внешнего класса. 

Например, запись вида:

```python
class Meta:
      ordering = ['id']
      t = Meta.title
```
приведет к ошибке, так как у вложенного пространства имен класса нет ссылки на внешнее пространство имен класса `Women`. Мало того, непосредственно во вложенном классе мы даже не можем явно указывать класс Women:

```python
class Meta:
      ordering = ['id']
      t = Women.title
```

то тоже приведет к ошибке, так как пространство имен `Women` на данный момент еще не создано. 

А вот обращаться к пространству имен `Women` внутри инициализатора класса `Meta` можно:

```python
class Meta:
        ordering = ['id']
 
        def __init__(self, access):
            self._access = access
            self._t = Women.title
```

Но, обычно, вложенные классы содержат изолированный код, который доступен только из внешнего класса.

Именно так следует проектировать программы при использовании вложенных классов.

Подобные вложения служат исключительно для удобства программирования.

Какого-то особого, сакрального смысла во вложенных классах нет и все что можно делать через вложения, также можно реализовать и без них, используя обычные, независимые объявления. 

Но вы вполне можете столкнуться с ними в других проектах, в частности, как я отмечал вначале, во веб-фреймворках.